﻿using System;
using System.Linq;
using System.Collections.Generic;
using NLite.Data.Dialect.Function;
using NLite.Data.SqlTypes;
using NLite.Collections;
using NLite.Reflection;
using System.Data.Common;
using System.Data;
using NLite.ComponentModel;

namespace NLite.Data.Dialect
{
    public abstract class Dialect
    {
        public Dialect()
        {
            RegisterColumnTypes();
            RegisterStringFunction();
            RegisterMathFunction();
            RegisterDateFunction();
        }

        #region Quote support
        public virtual char CloseQuote { get { return ']'; } }
        public virtual char OpenQuote { get { return '['; } }
        public virtual bool IsQuoted(string name)
        {
            return ( name[0] == OpenQuote && name[name.Length - 1] == CloseQuote) || !Keywords.Contains(name);
        }
        public virtual string Quote(string name)
        {
            if (name.IndexOf(".") > 0)
            {
                var parts = name.Split('.');
                return string.Join(".", parts.Select(p => IsQuoted(p) ? p : InternalQuote(p)).ToArray());
            }

            return IsQuoted(name) ? name :InternalQuote(name);
        }

        private string InternalQuote(string name)
        {
            
            var quotedName = name.Replace(OpenQuote.ToString(), new string(OpenQuote, 2));

            if (OpenQuote != CloseQuote)
                quotedName = name.Replace(CloseQuote.ToString(), new string(CloseQuote, 2));

            return OpenQuote + quotedName + CloseQuote;
        }

      
        #endregion

        #region keyword support
        public readonly HashSet<string> Keywords = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
      
        #endregion

        #region database function mapping support
        private readonly IDictionary<string, IFunctionView> sqlFunctions = new Dictionary<string, IFunctionView>(StringComparer.InvariantCultureIgnoreCase);
        protected abstract void RegisterStringFunction();
        protected abstract void RegisterMathFunction();
        protected abstract void RegisterDateFunction();

        public void RegisterFunction(string fnName, IFunctionView fn)
        {
            if (sqlFunctions.ContainsKey(fnName))
                sqlFunctions.Remove(fnName);
            sqlFunctions[fnName] = fn;
        }

        public IFunctionView GetFunction(string fnName)
        {
            if (!sqlFunctions.ContainsKey(fnName))
                throw new NotSupportedException(GetType().Name + " not support sql funcion:" + fnName);
            return sqlFunctions[fnName];
        }
        #endregion

        #region database type mapping support
        protected virtual void RegisterColumnTypes()
        {
        }

        private readonly TypeNames typeNames = new TypeNames();
        public virtual string GetTypeName(SqlType sqlType)
        {
            string result = null;
            if (sqlType.Length.HasValue || sqlType.Precision.HasValue || sqlType.Scale.HasValue)
            {
                var length = sqlType.Length ?? DbConfig.DefaultLength;
                var precision = sqlType.Precision ?? DbConfig.DefaultPrecision;
                var scale = sqlType.Scale ?? 0;

                result = typeNames.Get(sqlType.DbType,length, precision, scale);
            }
            else
                result = typeNames.Get(sqlType.DbType);

            if (string.IsNullOrEmpty(result))
            {
                result = typeNames.Get(sqlType.DbType, DbConfig.DefaultLength, DbConfig.DefaultPrecision, DbConfig.DefaultScale);
            }

            if (result == null)
            {
                throw new DAOException(string.Format("No type mapping for SqlType {0}", sqlType));
            }

            return result;
        }

        protected void RegisterColumnType(DbTypeCode code, int size, string name)
        {
            typeNames.Put(code, size, name);
        }

        protected void RegisterColumnType(DbTypeCode code, string name)
        {
            typeNames.Put(code, name);
        }
        #endregion

        #region ToSqlString
        public string ToSqlString(IDataParameter p)
        {
            var value = p.Value;
            var dbType = p.Identity.Type.DbType;

            return ToSqlString(dbType, value);
        }

        public virtual string ToSqlString(DbTypeCode? dbType, object value)
        {
            if (object.Equals(null, value))
                return "NULL";

            var type = value.GetType();
            var code = Type.GetTypeCode(type);
            if (code == TypeCode.DBNull)
                return "NULL";

            if (type.IsNullable())
            {
                type = Nullable.GetUnderlyingType(type);
                value = value.GetProperty("Value");
            }
            else if (type.IsEnum)
            {
                value = Convert.ChangeType(value, Enum.GetUnderlyingType(type));
                type = value.GetType();
            }

            var strValue = value.ToString();

            if (dbType.HasValue)
            {
                switch (dbType.Value)
                {
                    case DbTypeCode.Boolean:
                        var b = (bool)value;
                        return b ? "1" : "0";
                    case DbTypeCode.Byte:
                    case DbTypeCode.Int16:
                    case DbTypeCode.Int32:
                    case DbTypeCode.Int64:
                    case DbTypeCode.UInt16:
                    case DbTypeCode.UInt32:
                    case DbTypeCode.UInt64:
                    case DbTypeCode.Single:
                    case DbTypeCode.Decimal:
                    case DbTypeCode.Double:
                    case DbTypeCode.Currency:
                    case DbTypeCode.SByte:
                        return strValue;
                    case DbTypeCode.AnsiString:
                    case DbTypeCode.AnsiStringFixedLength:
                    case DbTypeCode.String:
                    case DbTypeCode.StringFixedLength:
                    case DbTypeCode.Clob:
                    case DbTypeCode.NClob:
                    case DbTypeCode.Time:
                    case DbTypeCode.Date:
                    case DbTypeCode.DateTime:
                    case DbTypeCode.DateTime2:
                    case DbTypeCode.DateTimeOffset:
                    case DbTypeCode.Guid:
                    case DbTypeCode.Xml:
                        return "\'" + strValue + "\'";
                }

            }
            else
            {
                code = Type.GetTypeCode(type);
                switch (code)
                {
                    case TypeCode.Boolean:
                         var b = (bool)value;
                        return b ? "1" : "0";
                    case TypeCode.Byte:
                    case TypeCode.Int16:
                    case TypeCode.Int32:
                    case TypeCode.Int64:
                    case TypeCode.UInt16:
                    case TypeCode.UInt32:
                    case TypeCode.UInt64:
                    case TypeCode.Single:
                    case TypeCode.Decimal:
                    case TypeCode.Double:
                    case TypeCode.SByte:
                        return strValue;
                    case TypeCode.String:
                    case TypeCode.DateTime:
                        return "\'" + strValue + "\'";
                    default:
                        if(value is DateTime)
                            return "\'" + strValue + "\'";
                        return strValue;
                        
                }
            }
            return strValue;
        }
        #endregion

        public virtual bool SupportsCollectionOperation(CollectionOperator type)
        {
            return true;
        }

        public virtual string FormatCollectionOperation(CollectionOperator type)
        {
            return type.GetDescription();
        }

        #region select support
        public virtual string IdentitySelectString
        {
            get
            {
                throw new NotSupportedException(GetType().Name + " not support identity key generation");
            }
        }
        public abstract void BuildSelect(NodeWriter visitor, SelectNode select);
        #endregion

        
    }

   
}
